////////////////////////////////////////////////////////////////////
//   
//   File: connect4_chapel.chpl 
//   Team: Patrick Copeland, Deanna Fink, Jordan Hildebrandt
//   Date: 8 February 2012
//   Time: 23:20
//   Desc: An implementation of Connect4 minimax player in Chapel
//
////////////////////////////////////////////////////////////////////

use connect4_chapel, Vector;

class MiniMaxPlayer{

   var movesMade: int ;
   var alpha: int ;
   var beta: int ;
    var playerSymbol: string ;
   var opponentSymbol: string ;
   /*
    Sets up playerSymbol and totalTime instance variables.

    */

    proc MiniMaxPlayer(playerSymbol){

        this.playerSymbol = playerSymbol;
        //var totalTime = totalTime

        if (playerSymbol == 'x'){
            this.opponentSymbol = 'o';
      }

        else {
            this.opponentSymbol = 'x';
      }
        this.movesMade = 0;

        this.alpha = -1000000;

        this.beta = 1000000;
   }


    /*

    Returns the column of the desired move

    */

    proc getMove(state){

        if (this.movesMade == 0){

            this.movesMade += 1;

            return 3;
        }

        else if (this.movesMade == 1){
            this.movesMade += 1;
            if (getPiece(state, 2, 5) == '_'){
                return 2;
            }
            else{
                return 4;
            }
      }
        else {
            this.movesMade += 1;

            // if (timeLeft < 15){

                // for i in [0..7]{

                    // if (isValidMove(state, i)){

                        // return i;
               // }
            // }
         // }
            // else {

                return this.minimaxDecision(state, 5);
         // }
      }
}

    /*

    ply is how far down the tree you've gone

    state is the state you're currently working with, somewhere in the tree

    Returns a column number indicating the move to make.

    Ultimately, this will be minimax with alpha-beta pruning, but get plain

    old minimax working first, and save a copy of it in case you mess something

    up while working on alpha-beta pruning.

    */
	proc minimaxDecision(state, ply){
      
        var bestMove: int = 0;
        var maxValue: int = 0;
		var possMaxValue: int;

        for i in [0..NUM_COLS-1] {
           
           if (isValidMove(state, i)) {
				var possState : [0..NUM_COLS-1][0..NUM_ROWS-1] string;
				possState = this.expandState(state, this.playerSymbol, i);

                possMaxValue = this.maxValue(possState, this.alpha, this.beta, ply);

                writeln("\n\nPossMaxVal:", possMaxValue, "\n\n");

				printState(possState);

				maxValue = max(maxValue, possMaxValue);

				if (maxValue == possMaxValue){
					bestMove = i;
				}
			}
		}

        return bestMove;
	}
        
        //Will call evalState and expandState


   proc maxValue(state, alpha, beta, ply):int{

        var val: int = -1000000;
        var newVal: int;
      //var listOfMoves: string[];
      
        if (ply == 0){
            return (this.evalState(state));
      }
        else{
            if (isTerminalState(state) != "*"){
                return (this.evalState(state));
         }
            else{
                
                //listOfMoves = this.expandState(state, this.playerSymbol); // state, column of move
                
                for i in [0..NUM_COLS-1] {
               if (isValidMove(state, i)) {
                    var possState : [0..NUM_COLS-1][0..NUM_ROWS-1] string;
                    possState = this.expandState(state, this.playerSymbol, i);
                       newVal = this.minValue(possState, alpha, beta, ply - 1);
                   
                       val = max(val, newVal);
   
                         if (val >= beta){
                           return val;
                       }
                       this.alpha = (max(alpha, val));
                    }
            }
         }
                        
            return val;
      }
   }   

    proc minValue(state, alpha, beta, ply):int{


        var val: int = 1000000;
      var newVal: int;
      //var listOfMoves: string[];
      
        if (ply == 0){
            return (this.evalState(state));
      }

        else{ 

            if (isTerminalState(state) != "*"){
                return (this.evalState(state));
         }

            else{
                //listOfMoves = this.expandState(state, this.playerSymbol);  // state, column of move
            
                for i in [0..NUM_COLS-1] {
                   if (isValidMove(state, i)) {
                    var possState : [0..NUM_COLS-1][0..NUM_ROWS-1] string;
                    possState = this.expandState(state, this.playerSymbol, i);
                       newVal= this.maxValue(possState, alpha, beta, ply - 1);
                
                         val = min(val, newVal);
                       if val <= alpha{
                           return val;
                  }

                    }   this.beta = min(beta, val);
            }
         }
            return val;
      }
   }
         
   proc sequence(direction, n, state, col, row, piece){
      
        if (direction(3, state, col, row) == piece){
            return 250;
      }

        else if (direction(2, state, col, row) == piece){
            return 25;
      }

        else if (direction(1, state, col, row) == piece){
            return 1;
      }

        return 0;
   }
    /*

    Gives a numerical evaluation of the given state.

    A positive score is good for this player - the higher the better.

    A negative score is bad for this player - the lower the worse.

    Always return a score from the perspective of *this* player

    (the "self" player), even in the midst of the minimax algorithm.

    Make sure evalState is zero-sum.  That is, if this player evaluates

    a state as 65, then the other player if using the same evaluation

    function would evaluate it to -65.

    */

    proc evalState(state){

        var value: int = 0;

        //check horizontal
        for row in [0..5] {
			for col in [0..6] {
				if col <= 3 {
					if isNSeqHoriz(4, state, col, row, this.playerSymbol) {
							value = value + 200;
					}
					if ((state[col+3][row]==this.playerSymbol)&&(state[col+2][row]=='_')&&isNSeqHoriz(2, state, col, row, this.playerSymbol)) {
					  value = value + 75;
					}
					if ((state[col][row]==this.playerSymbol)&&(state[col+1][row]=='_')&&isNSeqHoriz(2, state, col+2, row, this.playerSymbol)) {
					  value = value + 75;
					}
				}
				
				if col <= 4 {
				   if isNSeqHoriz(3, state, col, row, this.playerSymbol) {
					  value = value + 100;
					}
				   
				}
				
				if col <=5 {
				   if isNSeqHoriz(2, state, col, row, this.playerSymbol) {
					  value = value + 25;
				   }
				}
			}
		}

        //check vertical
        for col in [0..6] {
			for row in [0..5] {
				if row <= 2 {
					if isNSeqVert(4, state, col, row, this.playerSymbol) {
						value = value + 200;
					}
				}
		   
				if row <= 3 {
					if isNSeqVert(3, state, col, row, this.playerSymbol) {
						value = value + 100;
					}
				}
              
              
              if row <= 4 {
                 if isNSeqVert(2, state, col, row, this.playerSymbol) {
                    value = value + 25;
                 }
              }
           }
        }
        
        //check upDiag
        for col in [0..6] {
           for row in [0..5] {
              if row >= 2 && col <= 4 {
                 if isNSeqUpDiag(3, state, col, row, this.playerSymbol) {
                    value = value + 100;
                 }
              }
              
              if row >= 1 && col <= 5 {
                 if isNSeqUpDiag(2, state, col, row, this.playerSymbol) {
                    value = value + 25;
                 }
              }
           }
        }

        //check downDiag
		for col in [0..6] {
			for row in [0..5] {
				if row <= 3 && col <= 4 {
                 if isNSeqDownDiag(3, state, col, row, this.playerSymbol) {
                    value = value + 100;
                 }
				}
              
                if row <= 4 && col <= 5 {
					if isNSeqDownDiag(2, state, col, row, this.playerSymbol) {
						value = value + 25;
					}
				}
			}
        }

        return value;
   }


    /*

    Returns a list of all states that can be reached in one

    move by playerToMove, from the given state.

    playerToMove is 'x' or 'o'.

    playerToMove could be different from this.playerSymbol!

    Note: You might find it useful to have expandState return not just the

    new states, but rather pairings of column number (indicating the move)

    with the corresponding new state.  But this is up to you.

    */

    proc expandState(state, playerToMove, i){

      var copyState: [0..NUM_COLS-1][0..NUM_ROWS-1] string ;
      
      copyState = stateCopy(state);
        move(copyState, i, playerToMove);
        
        return copyState;

        
        //var expandLs:Vector(Vector);
        //expandLs = new Vector(string);
        
      //var copyState: [0][0] string;
      
        //for i in [0..7]{
        //    if (isValidMove(state, i)){
        //        copyState = stateCopy(state);
        //        move(copyState, i, playerToMove);
        //        expandLs.append((copyState, i));
      //   }
      //}
        //return expandLs;
      
   }
}